import path from "path-browserify";
import { PluginStaticResource } from "../plugin-static-resource/plugin-static-resource";

/**
 * 利用 systemjs 机制，在 resolve 时，从 systemjs-importmap 的 styles 加载对应 id 模块下的样式文件。解决所有模块都需要在 index.html 引入样式文件的问题
 *
 * @author chitanda
 * @date 2023-07-06 20:07:42
 * @export
 * @class SystemExtraCssLoader
 */
export class SystemExtraCssLoader {
  /**
   * 配置环境
   *
   * @author chitanda
   * @date 2023-07-06 17:07:16
   * @protected
   * @type {(Map<string, Record<string, string[] | string>>)}
   */
  protected static map: Map<
    PluginStaticResource,
    Record<string, string[] | string>
  > = new Map();

  /**
   * 已经输出过路径的 style 标签
   *
   * @author chitanda
   * @date 2023-03-23 10:03:38
   * @protected
   * @type {Map<string, null>}
   */
  protected static styleElementMap: Map<string, null> = new Map();

  /**
   * 配置文件加载 promise 对象
   *
   * @author chitanda
   * @date 2023-07-06 18:07:16
   * @protected
   * @type {(Promise<void> | null)}
   */
  protected static configLoadPromise: Promise<void> | null = null;

  /**
   * 是否已经加载过配置文件
   *
   * @author chitanda
   * @date 2023-07-07 16:07:54
   * @protected
   * @type {boolean}
   */
  protected static isLoaded: boolean = false;

  /**
   * 新增样式文件配置
   *
   * @author chitanda
   * @date 2023-07-06 20:07:45
   * @param {string} mateUrl 所有样式文件的基础路径，建议使用带有协议开头的 url 地址
   * @param {Record<string, string[] | string>} data 样式文件地址配置，key 为模块 id，value 为样式文件地址
   */
  static addImportStyleMap(
    mateUrl: string,
    data: Record<string, string[] | string>
  ): void {
    const staticResource = new PluginStaticResource(mateUrl);
    this.map.set(staticResource, data);
  }

  static init(): void {
    const me = this;
    const sourceResolve = System.constructor.prototype.resolve;
    // eslint-disable-next-line func-names
    System.constructor.prototype.resolve = function (
      id: string,
      parentUrl?: string
    ) {
      return Promise.resolve(sourceResolve.call(this, id, parentUrl)).then(
        async (module: unknown) => {
          if (module) {
            await me.loadStyle(id);
          }
          return module;
        }
      );
    };
    const sourceAddImportMap = System.constructor.prototype.addImportMap;
    // eslint-disable-next-line func-names
    System.constructor.prototype.addImportMap = function (
      map: Record<string, Record<string, string | string[]>>
    ) {
      sourceAddImportMap.call(this, map);
      if (map.styles) {
        SystemExtraCssLoader.addImportStyleMap(
          path.join(location.origin, location.pathname),
          map.styles
        );
      }
    };
  }

  /**
   * 根据模块 id 加载样式文件
   *
   * @author chitanda
   * @date 2023-07-06 17:07:28
   * @protected
   * @param {string} id
   * @return {*}  {Promise<void>}
   */
  protected static async loadStyle(id: string): Promise<void> {
    if (this.isLoaded === false) {
      this.isLoaded = true;
      this.configLoadPromise = this.resolveConfig();
    }
    if (this.configLoadPromise) {
      await this.configLoadPromise;
    }
    const all: Promise<void>[] = [];
    this.map.forEach((data, util) => {
      const urls = data[id];
      if (urls) {
        if (Array.isArray(urls)) {
          all.push(util.loadStyle(urls));
        } else {
          all.push(util.loadStyle([urls]));
        }
      }
    });
    await Promise.all(all);
  }

  /**
   * 加载样式静态资源配置，放置在 systemjs 的 importmap 中
   *
   * @author chitanda
   * @date 2023-07-06 17:07:39
   * @protected
   * @return {*}  {Promise<void>}
   */
  protected static async resolveConfig(): Promise<void> {
    const doms = document.querySelectorAll("script");
    const list = Array.from(doms);
    const all: unknown[] = list.map(async (dom) => {
      if (dom.type === "systemjs-importmap") {
        const res = await System.constructor.prototype.fetch(dom.src);
        if (res.ok) {
          const data = await res.json();
          if (data.styles) {
            const staticResource = new PluginStaticResource(dom.src);
            this.map.set(staticResource, data.styles);
          }
        }
      }
    });
    await Promise.all(all);
    if (this.configLoadPromise) {
      this.configLoadPromise = null;
    }
  }
}
